import mod_direction,mod_vector
from priodict import priorityDictionary
import fov
import mod_verbs,mod_ghost

class Brain(object):
    def __init__(self,actor,memory):
        self.actor = actor
        self.distanceMap = {}
        self.visibleTiles = set()
        self.memory = memory
        memory.addBrain(self)

    def generateDistanceMap(self,structuralOnly=True,visibleTilesOnly=False):
        actor = self.actor
        limit = None
        distanceMap = {}
        previous = {}
        spacesToVisit = priorityDictionary()
        
        level = actor.location[0]
        levelMemory = self.memory.getLevelMemory(level.number)
        
        currentSpace = actor.location[1]
        spacesToVisit[currentSpace] = 0
        
        for space in spacesToVisit:
            finalDistance = spacesToVisit[space]
            if limit == None or finalDistance <= limit:
                distanceMap[space] = finalDistance
                #if space == end: break
                for dir in mod_direction.ALLDIRS:
                    neighbor = mod_direction.getNeighbor(space,dir)
                    if neighbor in level.spaceMap:
                        
                        # Check if there's a passable space in this direction:
                        if neighbor != None and not (visibleTilesOnly and neighbor not in levelMemory.visibleTiles):
                            entity = level.entityMap.get(neighbor)
                            if (not entity) or (structuralOnly and not entity.structural):
                                newDist = finalDistance+dir.cost
                                if neighbor in distanceMap:
                                    if newDist < distanceMap[neighbor]:
                                        raise ValueError, "Dijkstra: found better path to already-final vertex"
                                elif neighbor not in spacesToVisit or newDist < spacesToVisit[neighbor]:
                                    spacesToVisit[neighbor] = newDist
                                    if not limit or newDist <= limit:
                                        previous[neighbor] = space
                                    
        self.distanceMap = distanceMap
        self.previousMap = previous

    def findShortestPath(self,destination):
        coords = self.actor.location[1]
        if destination in self.previousMap:
            path = []
            start = coords
            while destination != start:
                path.append(destination)
                destination = self.previousMap[destination]
            path.reverse()
            return path
        else:
            return []
        
    def getReachableTiles(self):
        retVal = set()
        for tile,distance in self.distanceMap.iteritems():
            if distance<=self.actor.AP:
                retVal.add(tile)
        return retVal
    
    def getDistance(self,targetCoords):
        return self.distanceMap.get(targetCoords)
    
    def canReach(self,newCoords):
        distance = self.distanceMap.get(newCoords)
        return (distance and distance<=self.actor.AP)
    
    def move(self,dir):
        mod_verbs.staticMoveVerb.doIt(self.actor,None,dir)
    
    def travel(self,newCoords):
        actor = self.actor
        startCoords = actor.location[1]
        for space in self.findShortestPath(newCoords):
            offset = mod_vector.subtractVectors(space,actor.location[1])
            dir = mod_direction.getDirFromOffset(offset)
            if dir.cost > actor.AP:
                # We're out of AP.
                break
            self.move(dir)
            if (not actor.location) or actor.location[1] != space:
                # We died, or our move was interrupted.
                break
        actor.generateNeighbors()
        return (actor.location and actor.location[1] != startCoords) # Return true if we changed position.
        
    
    def updateVisibility(self,otherLevel=None):
        if self.actor.location:
            self.generateVisibility()
            self.memory.updateVisibility(self.actor.location[0])
        if otherLevel:
            self.memory.updateVisibility(otherLevel)
        # Need to update visibility on both the new and the old levels, if we've moved
        # between two levels.
    
    def generateVisibility(self):
        self.visibleTiles.clear()
        if self.actor.location:
            level = self.actor.location[0]
            radius = max([level.width,level.height])
            fov.fieldOfView(radius, self)
        return self.visibleTiles
        
    def markTileVisible(self,coords):
        level = self.actor.location[0]
        self.visibleTiles.add(coords)
        
    def doesTileBlockVision(self,coords):
        entity = self.actor.location[0].entityMap.get(coords)
        return (entity and entity.prototype.blocksVision)
    
    def takeTurn(self):
        # May need to update visibility here.
        self.updateVisibility()
    
    def canSeeLocation(self,location):
        return self.memory.canSeeLocation(location)
    
    def onActorDeath(self):
        self.memory.removeBrain(self)
    

class BaseLevelMemory:
    def __init__(self,levelNum):
        self.visibleTiles = set()
        self.levelNum = levelNum
    
    def updateVisibility(self,memory,level,refreshAll=False):
        self.visibleTiles = set()
        for brain in memory.brains:
            if brain.actor.location:
                actorLevelNum = brain.actor.location[0].number
                if actorLevelNum == self.levelNum:
                    if refreshAll:
                        brain.generateVisibility()
                    self.visibleTiles.update(brain.visibleTiles)


class Memory:
    def __init__(self,levelMemoryClass):
        self.LevelMemoryClass = levelMemoryClass
        self.levelMemoryMap = {} # Maps level numbers to LevelMemorys
        self.brains = []
    
    def addBrain(self,brain):
        self.brains.append(brain)
    
    def removeBrain(self,brain):
        self.brains.remove(brain)
    
    def updateVisibility(self,level,refreshAll=False):
        levelMemory = self.getLevelMemory(level.number)
        levelMemory.updateVisibility(self,level,refreshAll)
    
    def isTileExplored(self,tile):
        return True
    
    def updateGhost(self,newGhost,level):
        # We may not want to refresh the visibility of every brain when
        # the ghost is propagated to more than one brain at once, since we will end up redoing
        # calculations.
        if isinstance(newGhost,mod_ghost.EntityGhost) and newGhost.blocksVision:
            self.updateVisibility(level,refreshAll=True)
        
    # Returns the levelMemory for the level. If it doesn't exist, it creates it.
    def getLevelMemory(self,levelNumber):
        levelMemory = self.levelMemoryMap.get(levelNumber)
        if not levelMemory:
            levelMemory = self.LevelMemoryClass(levelNumber)
            self.levelMemoryMap[levelNumber] = levelMemory
        return levelMemory
    
    def canSeeLocation(self,location):
        levelMemory = self.getLevelMemory(location[0])
        return (location[1] in levelMemory.visibleTiles)
